
#ifndef GSLFUNCTIONWRAPPER_H

#include <gsl_ext/gsl_ext_function.h>
#include <gsl_ext/gsl_ext_symbolic_function.h>

#include <gsl/gsl_multimin.h>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

/// Defines the interface for GSL/GSL++ function wrappers.
/**
 * This class defines the interface of C++ wrappers 
 * for GSL/GSL++ function objects. This high-level 
 * interface provides a more convenient way 
 * to access the GSL/GSL++ functions written in C.
 */
class GSLFunctionWrapper
{
 public:
  /// Defines the types for \f$C^0\f$, \f$C^1\f$ and \f$C^2\f$ functions.
  enum Type { F, FDF, FD2F };

  GSLFunctionWrapper() { }

  virtual ~GSLFunctionWrapper() { }

  /// Frees the memory associated with the wrapped function object.
  virtual void destroyWrappedObject() = 0;

  /// Returns the number of function evaluations.
  virtual unsigned int getFuncEvalCounter() const = 0;

  /// Returns the number of gradient evaluations.
  virtual unsigned int getGradEvalCounter() const = 0;

  /// Returns the number of Hessian evaluations.
  virtual unsigned int getHessEvalCounter() const = 0;

  /// Returns the type of the wrapped object (F, FDF or FD2F).
  virtual Type getType() const = 0;

  /// Returns the variable names used by the function.
  /**
   * For symbolic functions, this method returns 
   * the names appearing in the symbolic expression. 
   * If symbolic expression is not available, this 
   * method returns a list of names of the form 
   * \f$x_1,x_2,\dots,x_n\f$.
   * @param result the vector into which the variable names will be stored
   */
  virtual vector< string > &getVariableNames(vector< string > &result) const = 0;

  /// Returns the dimension of the function.
  virtual double n() const = 0;
 private:
  // These are not implemented, so using them is disallowed.
  GSLFunctionWrapper(const GSLFunctionWrapper &fw);
  GSLFunctionWrapper &operator=(const GSLFunctionWrapper &fw);
};

/// Defines a templated prototype for GSL/GSL++ function wrappers. 
template < typename T > class GSLFunctionWrapperT : public GSLFunctionWrapper
{
 public:
  GSLFunctionWrapperT(T *f):f_(f) { }
  
  virtual ~GSLFunctionWrapperT() { }
  
  /// Returns a pointer to the wrapped function object.
  operator T*() const { return f_; }
  
  unsigned int getFuncEvalCounter() const
  {
    return ((gsl_ext_multimin_function_params *)f_->params)->f_eval_counter;
  }
  
  unsigned int getGradEvalCounter() const
  {
    return ((gsl_ext_multimin_function_params *)f_->params)->df_eval_counter;
  }
    
  unsigned int getHessEvalCounter() const
  {
    return ((gsl_ext_multimin_function_params *)f_->params)->d2f_eval_counter;
  }

  vector< string > &getVariableNames(vector< string > &result) const
  {
    int i;
    gsl_ext_multimin_function_params *p;
    gsl_ext_multimin_symbolic_function_f_evaluator *e;
    bool symEval = true;
    int n;
    
    if(getType() == GSLFunctionWrapper::F)
    {
      n = f_->n;
      p = (gsl_ext_multimin_function_params *)f_->params;
      if(p->f_eval_type == GSL_EXT_FUN_EVAL_SYM)
      {
        e = (gsl_ext_multimin_symbolic_function_f_evaluator *)p->f_evaluator;
        for(i = 0; i < n; i++)
          result.push_back(e->var_names[i]);
      }
      else
        symEval = false;
    }
    else if(getType() == GSLFunctionWrapper::FDF)
    {
      n = f_->n;
      p = (gsl_ext_multimin_function_params *)f_->params;
      if(p->f_eval_type == GSL_EXT_FUN_EVAL_SYM)
      {
        e = (gsl_ext_multimin_symbolic_function_f_evaluator *)p->f_evaluator;
        for(i = 0; i < n; i++)
          result.push_back(e->var_names[i]);
      }
      else
        symEval = false;
    }
    else
    {
      n = f_->n;
      p = (gsl_ext_multimin_function_params *)f_->params;
      if(p->f_eval_type == GSL_EXT_FUN_EVAL_SYM)
      {
        e = (gsl_ext_multimin_symbolic_function_f_evaluator *)p->f_evaluator;
        for(i = 0; i < n; i++)
          result.push_back(e->var_names[i]);
      }
      else
        symEval = false;
    }
    
    if(symEval == false)
    {
      ostringstream ostr;
      for(i = 0; i < n; i++)
      {
        ostr.clear();
        ostr.str("");
        ostr<<"x"<<i + 1;
        result.push_back(ostr.str());
      }
    }
    
    return result;
  }
  
  double n() const { return f_->n; }
 protected:
  /// Defines the function object wrapped by this class.
  T *f_;
};

/// Defines the wrapper class for the gsl_multimin function struct.
class GSLFunctionWrapper_f : public GSLFunctionWrapperT< gsl_multimin_function >
{
public:
  GSLFunctionWrapper_f(gsl_multimin_function *f);
  void destroyWrappedObject();
  double f(const gsl_vector *x) const;
  Type getType() const;
};

/// Defines the wrapper class for the gsl_multimin function_fdf struct.
class GSLFunctionWrapper_fdf : public GSLFunctionWrapperT< gsl_multimin_function_fdf >
{
public:
  GSLFunctionWrapper_fdf(gsl_multimin_function_fdf *f);
  void destroyWrappedObject();
  double f(const gsl_vector *x) const;
  void df(const gsl_vector *x, gsl_vector *g) const;
  Type getType() const;
};

/// Defines the wrapper class for the gsl_ext_multimin function_fd2f struct.
class GSLFunctionWrapper_fd2f : public GSLFunctionWrapperT< gsl_ext_multimin_function_fd2f >
{
public:
  GSLFunctionWrapper_fd2f(gsl_ext_multimin_function_fd2f *f);
  void destroyWrappedObject();
  double f(const gsl_vector *x) const;
  void df(const gsl_vector *x, gsl_vector *g) const;
  void d2f(const gsl_vector *x, gsl_matrix *H) const;
  Type getType() const;
};

/// Implements factory methods for creating GSL/GSL++ function wrapper instances.
class GSLFunctionWrapperFactory
{
public:
  /// Returns a GSLFunctionWrapper_f instance for a gsl_multimin_function object.
  static GSLFunctionWrapper_f *getInstance(gsl_multimin_function *f);

  /// Returns a GSLFunctionWrapper_fdf instance for a gsl_multimin_function_fdf object.
  static GSLFunctionWrapper_fdf *getInstance(gsl_multimin_function_fdf *f);

  /// Returns a GSLFunctionWrapper_fd2f instance for a gsl_ext_multimin_function_fd2f object.
  static GSLFunctionWrapper_fd2f *getInstance(gsl_ext_multimin_function_fd2f *f);
};

#define GSLFUNCTIONWRAPPER_H

#endif
